#include <math.h>
#include "torrent_control.h"
#include <libtorrent/hasher.hpp>
#include <memory>
#include <cassert>

#if BOOST_VERSION < 103400
	namespace fs = boost::filesystem;
	fs::path::default_name_check(fs::no_check);
#endif

torrent_control::torrent_control(std::string torrent_name,
                    std::string save_path,
                    std::pair<int,int> port_range,
                    bool auto_buf,
                    std::string adress,
                    std::string port,
                    std::string file_name,
                    size_t max_simultaneous_downloads) 
: 
    critical_window(-1),
    auto_buffer(auto_buf),
    connection(adress,
               port,
               file_name,
               max_simultaneous_downloads),
    alerts_thread(
        boost::bind(&torrent_control::alerts_thread_worker, this))

{
    torrent_session.listen_on(port_range);

    libtorrent::add_torrent_params parameter;
    parameter.save_path = save_path; 
    const char* name = torrent_name.c_str();
    parameter.ti = new libtorrent::torrent_info(name); //FIXME
    torrent_session.add_torrent(parameter);

    libtorrent::session_settings settings;
    settings.unchoke_interval = 5; //FIXME? We should remove choking completely, if possible
    torrent_session.set_settings(settings);
    torrent_handle = torrent_session.get_torrents()[0];
    
    torrent_session.set_alert_mask(libtorrent::alert::progress_notification |
                                   libtorrent::alert::storage_notification);
    
    piece_src = std::vector<int>(num_pieces(),0);
}
void torrent_control::set_piece_src(size_t index, int src){
    piece_src[index] = src;
}


void torrent_control::on_download_complete(backup_server_connection::request req,
                                           std::vector<char*> buffers)
{
/*    std::cout << "received " << num_bytes << " bytes of data"
              << " | piece_size: " << req.piece_size 
              << " | index: " << req.index
              << " | count: " << req.count
              << std::endl;

    libtorrent::hasher hash(data, num_bytes);
    std::cerr << "downloaded data hash: " << hash.final() << std::endl;
    std::cerr << "actual     data hash: " << get_piece_hash(req.index) << std::endl; 
*/
    
   
    // sanity check: we should always get as many pieces as we requested
    assert(buffers.size() == req.count);

    for(size_t i = 0; i < buffers.size(); ++i)
    {
        torrent_handle.add_piece(req.index + i, buffers[i], 0);
        disp.post(boost::bind(&torrent_control::set_piece_src,this,req.index+i,1));
    }
}

void torrent_control::alerts_thread_worker()
{
    while(true)
    {
        std::auto_ptr<libtorrent::alert> a;
        a = torrent_session.pop_alert();
        while(a.get()) {
            // got piece_finished_alert
            if(libtorrent::piece_finished_alert* pfa = 
                dynamic_cast<libtorrent::piece_finished_alert*>(a.get()))
            {
                if(!piece_finished_callback.empty())
                {
                    piece_finished_callback(pfa->piece_index);
                }

            }
            // got hash_failed_alert
            else if(libtorrent::hash_failed_alert* hfa =
                    dynamic_cast<libtorrent::hash_failed_alert*>(a.get())){

                std::cerr << "hash failed alert: " <<  std::endl
                          << a->what() << ":" << a->message()<< std::endl;
            }
            // got some kind of alert that we won't handle
            else 
            {
#ifdef DEBUG
                std::cerr << "unhandled libtorrent event: " << std::endl 
                          << a->what() << ":" << a->message()<< std::endl;
#endif
            }

            a = torrent_session.pop_alert();
        }
        usleep(500);
    }
}

progress_info torrent_control::get_progress()
{
    progress_info progress_info;
    progress_info.priorities = torrent_handle.piece_priorities();
    progress_info.downloaded = torrent_handle.status().pieces;
    torrent_handle.get_download_queue(progress_info.piece_states);
    progress_info.piece_src = piece_src;

    return progress_info;
}

download_info torrent_control::get_download_info()
{
    libtorrent::session_status  status = torrent_session.status(); //FIXME: Threadsafe?
    download_info download_info;
    download_info.num_peers = status.num_peers;
    download_info.num_unchoked = status.num_unchoked;
    download_info.num_connections = torrent_session.num_connections();
    download_info.download_rate = status.download_rate;
    download_info.upload_rate = status.upload_rate;
    download_info.total_download = status.total_download;
    return download_info;
}

int torrent_control::read_data(size_t offset, size_t length, char* buffer)
{
    //Here we assume that 1 slot = 1 piece
    int piece_length = torrent_handle.get_torrent_info().piece_length();
    int slot_index = offset/piece_length;
    offset = offset % piece_length;
    if(auto_buffer == true){
       pre_buffer(offset+length,length); 
    }
    return torrent_handle.get_storage_impl()->read(buffer, slot_index, offset, length);

}

void torrent_control::pre_buffer(size_t offset, size_t length){
    int piece_length = torrent_handle.get_torrent_info().piece_length();
    size_t first_piece = offset/piece_length;
    size_t last_piece = ceil((offset+length)/piece_length);
    size_t count = last_piece - first_piece;
    if(first_piece < num_pieces()){
        if(first_piece + count > num_pieces()){
            count = num_pieces() - first_piece;
        }
        add_pieces_from_backup_server(first_piece,count);
    }
}

void torrent_control::add_pieces_from_backup_server(
                                  size_t piece_index,
                                  size_t count)
{
    if (piece_index >= 0) { //&& piece_index <= torrent_handle.status().num_pieces){
        backup_server_connection::request
            request(torrent_handle.get_torrent_info().piece_length(), piece_index, count);
        connection.send(request, 
            boost::bind(&torrent_control::on_download_complete, this, _1, _2));
    }
}

void torrent_control::set_critical_window(double time){
    critical_window = time;
}

void torrent_control::set_download_rate_limit(int download_rate){
    torrent_session.set_download_rate_limit(download_rate);
}

void torrent_control::set_upload_rate_limit(int upload_rate){
    torrent_session.set_upload_rate_limit(upload_rate);
}

